#include <arpa/inet.h>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <sys/types.h> //文件
#include <sys/stat.h>  //文件
#include <fcntl.h>     //文件
#include <unistd.h>    //文件
using namespace std;
#include "log_reader.h"
#include "except.h"

//该函数用来得出“wtmpx”文件与“unmatched_login_file”文件的大小，以字节为单位
long FileSize(const char *fname)
{
    struct stat statbuf;
    if(stat(fname,&statbuf)==0){
		return (float)(statbuf.st_size);
    }
    return -1;
}
long wtmpxSize = FileSize("./wtmpx"); //获得“wtmpx”文件的大小，以字节为单位
long unmatchedLoginFileSize = FileSize("./unmatched_login_file"); //获得“unmatched_login_file”文件的大小，以字节为单位

LogReader::LogReader()
{
    this->log_file="wtmpx";
    this->unmatched_login_file="unmatched_login_file";
}

LogReader::LogReader(string log_file, string unmatched_login_file)
{
    this->log_file=log_file;
    this->unmatched_login_file=unmatched_login_file;
}

//备份日志文件，将原始日志文件备份一份
void LogReader::backup() throw (BackupException){
	
	cout << "开始备份wtmpx文件..." << endl << endl;
	sleep(1);
	
	int wtmpxFd = open("./wtmpx",O_RDONLY);
	
	if(-1 == wtmpxFd){
		cout << "打开wtmpx文件失败！" << endl;
	}else{
		//开始备份
		//创建一个备份文件
		int wtmpxCopyFd = open("./wtmpxCopy",O_CREAT|O_RDWR,0666);
		
		if(-1 == wtmpxCopyFd){
			cout << "备份wtmpx文件失败" << endl;
		}
		
		//创建一个缓冲区
		char buf[4096] = {};
		//创建一个文件描述符记录读取到的字节数
		int len;
		
		while((len = read(wtmpxFd,buf,4096)) != 0){
			write(wtmpxCopyFd,buf,len);
			bzero(buf,4096); //清空缓冲区
		}
		close(wtmpxFd);
		close(wtmpxCopyFd);
	}
	
	cout << "备份wtmpx文件成功！" << endl << endl;
	sleep(1);
}

//读取上次未匹配的登入记录文件
void LogReader::readUnmatchedFile() throw (ReadException){
	
	cout << "开始读取上次未匹配的登入记录文件..." << endl << endl;
	sleep(1);
	
	LogRec log_rec; //用来保存读取到的登入和登出记录
	
	pid_t pid;  //用来保存读取到的进程ID（要转为网络字节序）
	short type; //用来保存读取到的登录类型（要转为网络字节序）
	int time;   //用来保持读取到的登录时刻（要转为网络字节序）
	
	int unmatchedLoginFileFd = open("./unmatched_login_file",O_RDONLY);
	//打开unmatchedLoginFileFd文件失败
	if(-1 == unmatchedLoginFileFd){
		cout<<"打开unmatchedLoginFileFd文件失败，请重试！"<< endl << endl;
	}
	
	cout<<"unmatchedLoginFileFd文件的大小为："<< unmatchedLoginFileSize << "个字节." << endl << endl;
	sleep(1);
	
	//利用for循环开始读“wtmpx”文件，将每一条读取到的登入或者登出记录存储在第64行定义的LogRec log_rec结构体中
	for(int begin = 0; begin < unmatchedLoginFileSize; begin += 372){
		
		lseek(unmatchedLoginFileFd,begin,SEEK_SET); //每一次读之前都先将文件指针移动到偏离文件头SEEK_OUT begin个字节的位置上再开始读
		read(unmatchedLoginFileFd, (char *)& log_rec.log_name,32);
		
		lseek(unmatchedLoginFileFd,36,SEEK_CUR);
		read(unmatchedLoginFileFd, (char *)& pid,4);
		pid = ntohl(pid);
		log_rec.pid = pid;
		
		read(unmatchedLoginFileFd, (char *)& type,2);
		type = ntohs(type);
		log_rec.log_type = type;
		
		lseek(unmatchedLoginFileFd,6,SEEK_CUR);
		read(unmatchedLoginFileFd, (char *)& time,4);
        time = ntohl(time);
        log_rec.log_time = time;
        
        lseek(unmatchedLoginFileFd,30,SEEK_CUR);
        read(unmatchedLoginFileFd, (char *)& log_rec.log_ip, 32);
        
        if(type == 7){
        	login_record.push_back(log_rec);
        	cout << "成功存入一条登入记录..." << endl << endl;
        	sleep(0.03);
        }else if(type == 8){
        	logout_record.push_back(log_rec);
        	cout << "成功存入一条登出记录..." << endl << endl;
        	sleep(0.03);
        }
	}
	
	close(unmatchedLoginFileFd);
	
	cout << "成功读取上次未匹配的登入记录文件！" << endl << endl;	
	sleep(1);

}
	
//读取备份日志及记录文件，提取读取的数据，生成登入或登出的日志记录，并将记录放入相应的集合中
void LogReader::readBackupFile() throw (ReadException){
	
	cout << "开始读取备份数据文件wtmpxCopy..." << endl << endl;
	sleep(1);
	
	LogRec log_rec; //用来保存读取到的登入和登出记录
	
	pid_t pid;  //用来保存读取到的进程ID（要转为网络字节序）
	short type; //用来保存读取到的登录类型（要转为网络字节序）
	int time;   //用来保持读取到的登录时刻（要转为网络字节序）
	
	int wtmpxCopyFd = open("./wtmpxCopy",O_RDONLY);
	//打开wtmpxCopy文件失败
	if(-1 == wtmpxCopyFd){
		cout<<"打开wtmpxCopy文件失败，请重试！"<<endl;
	}
	
	cout<<"wtmpxCopy文件的大小为："<< wtmpxSize << "个字节" <<endl;
	
	//利用for循环开始读“wtmpx”文件，将每一条读取到的登入或者登出记录存储在第64行定义的LogRec log_rec结构体中
	for(int begin = 0; begin < wtmpxSize; begin += 372){
		
		lseek(wtmpxCopyFd,begin,SEEK_SET); //每一次读之前都先将文件指针移动到偏离文件头SEEK_OUT begin个字节的位置上再开始读
		read(wtmpxCopyFd, (char *)& log_rec.log_name,32);
		
		lseek(wtmpxCopyFd,36,SEEK_CUR);
		read(wtmpxCopyFd, (char *)& pid,4);
		pid = ntohl(pid);
		log_rec.pid = pid;
		
		read(wtmpxCopyFd, (char *)& type,2);
		type = ntohs(type);
		log_rec.log_type = type;
		
		lseek(wtmpxCopyFd,6,SEEK_CUR);
		read(wtmpxCopyFd, (char *)& time,4);
        time = ntohl(time);
        log_rec.log_time = time;
        
        lseek(wtmpxCopyFd,30,SEEK_CUR);
        read(wtmpxCopyFd, (char *)& log_rec.log_ip, 32);
        
        if(type == 7){
        	login_record.push_back(log_rec);
        	cout << "成功存入一条登入记录..." << endl << endl;
        	sleep(0.03);
        }else if(type == 8){
        	logout_record.push_back(log_rec);
        	cout << "成功存入一条登出记录..." << endl << endl;
        	sleep(0.03);
        }
	}
	
	close(wtmpxCopyFd);
	
	cout << "成功读取备份数据文件wtmpxCopy！" << endl << endl;	
	sleep(1);

}

//将用户登入记录和登出记录匹配成一个完整的登录记录
void LogReader::match(){
	
	cout << "开始将登录日志记录进行匹配..." << endl <<  endl;
	sleep(1);
	
    LogRec logout_rec;             //保存登出记录的结构体
    LogRec login_rec;              //保存登入记录的结构体
    MatchedLogRec matched_log_rec; //保存匹配好的登录记录的结构体
    list<LogRec>::iterator iter1;  //iter1是保存登出记录的集合的迭代器
    list<LogRec>::iterator iter2;  //iter2是保存登入记录的集合的迭代器
		
	//开始匹配
    iter1 = logout_record.begin();
    while(iter1 != logout_record.end())
    {
        logout_rec = *iter1;
        iter2 = login_record.begin();
        while(iter2 != login_record.end())
        {
            login_rec = *iter2;
            if(strcmp(login_rec.log_name, logout_rec.log_name) == 0 &&
               login_rec.pid == logout_rec.pid &&
               strcmp(login_rec.log_ip, logout_rec.log_ip) == 0)
            {
                strcpy(matched_log_rec.log_name, logout_rec.log_name);
                matched_log_rec.pid = logout_rec.pid;
                matched_log_rec.login_time = login_rec.log_time;
                matched_log_rec.logout_time = logout_rec.log_time;
                matched_log_rec.duration = matched_log_rec.logout_time - matched_log_rec.login_time;
                strcpy(matched_log_rec.log_ip, logout_rec.log_ip);
					
                matched_log_record.push_back(matched_log_rec);//将匹配好的登录记录matched_log_rec存入到matched_log_record集合的最后一个位置中去

                iter1=logout_record.erase(iter1); //利用erase函数删除括号里所指位置的元素，并返回当前删除元素的下一个元素的迭代器
                iter2=login_record.erase(iter2);
                iter1--;
                break;
            }
            else
            {
                if(login_record.empty())
                {
                    throw ClientException("Login_record empty cause match failed");
                }
            }
            iter2++;
        }
        if(iter2 == login_record.end())  //If there is no login log and logout match, leave the logout record.
        {
            iter1 = logout_record.erase(iter1);
            continue;
        }
        iter1++;
    }
    cout << "成功将登录日志记录信息进行匹配！" << endl << endl;	
	cout << "匹配好的登录日志记录信息共: " << matched_log_record.size() << "条." << endl << endl;
	sleep(1);
}
	
//保存上次未匹配的登入记录日志信息
void LogReader::saveUnmatchedLogin() throw(SaveException){
	
	cout << "开始保存未匹配的登入记录到文件中..." << endl << endl;
	sleep(1);
	
    try
    {
        ofstream fout;
        LogRec login_rec;
        list<LogRec>::iterator itor;
        itor = login_record.begin();
        pid_t pid;
        short type;
        int time;

        fout.open("unmatched_login_file", ios::trunc|ios::out|ios::binary);     //everytime clear this file.
        if(fout.fail())
        {
            throw SaveException("创建unmatched_login_file文件失败，请重试！");
            exit(1);
        }
        while(itor != login_record.end())
        {
            login_rec = *itor;
            fout.write(login_rec.log_name, 32);
            pid = login_rec.pid;
            fout.write((char *)&pid, 4);
            type = login_rec.log_type;
            fout.write((char *)&type, 2);
            time = login_rec.log_time;
            fout.write((char *)&time, 4);
            fout.write(login_rec.log_ip, 32);
            itor++;
        }
        fout.close();
        login_record.clear(); //写入文件之后将登入记录集合清空，保证下次顺利读取
        
    }
    catch(ClientException & e)
    {
        cout << e.what() << endl;
    }
    cout << "成功保存未匹配的登入记录到文件!" << endl << endl;	
	sleep(1);
}

//正式进行读取工作的函数
list<MatchedLogRec>& LogReader::readLog() throw (ClientException){
	
	cout << "开始数据读取..." << endl << endl;
	sleep(1);
	
	backup();            //备份文件
	readUnmatchedFile(); //读取上次为匹配的登入记录文件
	readBackupFile();    //读取备份文件
	match();             //进行登入记录和登出记录的数据匹配
	saveUnmatchedLogin();//保存未匹配的登入记录文件
	
	cout << "数据读取完成！" << endl << endl;
	sleep(1);
	
	//返回匹配好的登入登出记录的集合（数据在readBackupFile()函数里给了，应该吧。。。）
	return matched_log_record;	
}

